/**
 * 
 */
package org.xlab.baseframework.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;
import org.xlab.baseframework.annotation.Loggable;
import org.xlab.baseframework.dao.api.ISystemCodeDAO;
import org.xlab.baseframework.domain.ISystemUserDomain;
import org.xlab.baseframework.domain.SystemCode;
import org.xlab.baseframework.domain.enumerate.EnumDeletedIndentifying;
import org.xlab.baseframework.domain.view.SystemCodeVO;
import org.xlab.baseframework.event.SystemCodeChangedEvent;
import org.xlab.baseframework.event.SystemCodeRemovedEvent;
import org.xlab.baseframework.exception.ObjectNotFoundException;
import org.xlab.baseframework.exception.SystemCodeDuplicateException;
import org.xlab.baseframework.exception.SystemCodeNotFoundException;
import org.xlab.baseframework.exception.TooManyObjectsFoundException;
import org.xlab.baseframework.service.api.ISystemCodeService;
import org.xlab.baseframework.system.SystemEventPublisher;
import org.xlab.baseframework.system.SystemRuntimeEnvironment;

/**
 * @author MaKuBEX
 * 
 */
@Service
public class SystemCodeServiceImpl implements ISystemCodeService {

	/**
	 * @serialField
	 */
	private static final long serialVersionUID = 5745570844404459591L;
	private static final Logger log = Logger
			.getLogger(SystemCodeServiceImpl.class);
	@Autowired(required = true)
	@Qualifier("bpf_systemCodeServiceMessageSource")
	private MessageSource messageSource;
	@Autowired(required = true)
	@Qualifier("bpf_systemCodeDAO")
	private ISystemCodeDAO systemCodeDAO;
	@Autowired(required = true)
	@Qualifier("bpf_systemEventPublisher")
	private SystemEventPublisher systemEventPublisher;

	/**
	 * @see org.xlab.baseframework.service.api.IBaseService#convertToVO(org.xlab.baseframework.domain.INormalDomain)
	 */
	public SystemCodeVO convertToVO(SystemCode entity) {
		SystemCodeVO vo = new SystemCodeVO(entity);
		return vo;
	}

	/**
	 * @see org.xlab.baseframework.service.api.IBaseService#convertToVOList(java.util.Collection)
	 */
	public List<SystemCodeVO> convertToVOList(
			Collection<SystemCode> entityCollection) {
		List<SystemCodeVO> result = new ArrayList<SystemCodeVO>(
				entityCollection.size());
		for (SystemCode code : entityCollection) {
			result.add(this.convertToVO(code));
		}
		return result;
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#createSystemCode(java.lang.String,
	 *      java.lang.String, java.lang.Long,
	 *      org.xlab.baseframework.domain.SystemCode,
	 *      org.xlab.baseframework.domain.ISystemUserDomain)
	 */
	@Loggable
	public void createSystemCode(String code, String description,
			Long sequence, SystemCode category, ISystemUserDomain createdBy)
			throws SystemCodeDuplicateException {
		try {
			this.findByCode(code, false);
			throw new SystemCodeDuplicateException();
		} catch (SystemCodeNotFoundException e) {
			String cate = "root";
			if (category != null)
				cate = category.getCode();
			String message = this.messageSource.getMessage(
					"info.service.syscode.creating", new Object[] { code,
							description, cate },
					"Creating SYSTEM code with code [" + code
							+ "], description [" + description
							+ "], category [" + cate + "].",
					Locale.getDefault());
			log.debug(message);
			Date current = new Date();
			SystemCode newSysCode = new SystemCode(code, category, description,
					sequence, EnumDeletedIndentifying.N);
			newSysCode.setCreatedTime(current);
			newSysCode.setCreatedBy(createdBy.getUserCode());
			newSysCode.setCreatedByName(createdBy.getRealName());
			newSysCode.setLastUpdatedBy(createdBy.getUserCode());
			newSysCode.setLastUpdatedTime(current);
			newSysCode.setLastUpdatedByName(createdBy.getRealName());
			this.systemCodeDAO.save(newSysCode);
			this.systemEventPublisher.publishEvent(new SystemCodeChangedEvent(
					this, newSysCode));
		}
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#deleteSystemCode(org.xlab.baseframework.domain.SystemCode,
	 *      org.xlab.baseframework.domain.ISystemUserDomain)
	 */
	@Loggable
	public void deleteSystemCode(SystemCode systemCode,
			ISystemUserDomain deletedBy) {
		String message = null;
		message = this.messageSource.getMessage(
				"info.service.syscode.deleting",
				new Object[] { systemCode.getCode() }, "Deleting SYSTEM code["
						+ systemCode.getCode() + "].", Locale.getDefault());
		log.debug(message);
		systemCode.setLastUpdatedBy(deletedBy.getUserCode());
		systemCode.setLastUpdatedByName(deletedBy.getRealName());
		systemCode.setLastUpdatedTime(new Date());
		this.systemCodeDAO.logicRemove(systemCode);
		this.systemEventPublisher.publishEvent(new SystemCodeRemovedEvent(this,
				systemCode));
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#findAll()
	 */
	public List<SystemCode> findAll() throws SystemCodeNotFoundException {
		List<SystemCode> results = null;
		try {
			results = this.systemCodeDAO.findAll();
			Collections.sort(results);
			return results;
		} catch (ObjectNotFoundException e) {
			throw new SystemCodeNotFoundException(e);
		}
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#findAllByDeletedInd(org.xlab.baseframework.domain.enumerate.EnumDeletedIndentifying)
	 */
	public List<SystemCode> findAllByDeletedInd(
			EnumDeletedIndentifying deletedInd)
			throws SystemCodeNotFoundException {
		SystemCode exampleInstance = new SystemCode();
		exampleInstance.setDeletedInd(deletedInd);
		try {
			List<SystemCode> results = this.systemCodeDAO
					.findAllByExample(exampleInstance);
			Collections.sort(results);
			return results;
		} catch (ObjectNotFoundException e) {
			throw new SystemCodeNotFoundException(e);
		}
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#findByCode(java.lang.String)
	 */
	public SystemCode findByCode(String code, boolean useCache)
			throws SystemCodeNotFoundException, SystemCodeDuplicateException {
		String message = null;
		SystemCode result = null;
		try {
			if (useCache) {
				message = this.messageSource
						.getMessage("info.service.syscode.findingbycode_cache",
								new Object[] { code },
								"Finding SYSTEM code by code[" + code
										+ "] from cache...",
								Locale.getDefault());
				log.debug(message);
				result = SystemRuntimeEnvironment.getCode(code);
			} else {
				message = this.messageSource.getMessage(
						"info.service.syscode.findingbycode",
						new Object[] { code }, "Finding SYSTEM code[" + code
								+ "].", Locale.getDefault());
				log.debug(message);
				result = this.systemCodeDAO.findByCode(code);
			}
			return result;
		} catch (ObjectNotFoundException e) {
			message = this.messageSource.getMessage(
					"warn.service.syscode.findingbycode.noresult",
					new Object[] { code },
					"Can not find any SYSTEM code instance by code [" + code
							+ "].", Locale.getDefault());
			log.warn(message);
			throw new SystemCodeNotFoundException(e);
		} catch (TooManyObjectsFoundException e) {
			message = this.messageSource.getMessage(
					"info.service.syscode.findingbycode.toomuch",
					new Object[] { code },
					"More than one SYSTEM code were found by code[" + code
							+ "].", Locale.getDefault());
			log.error(message);
			throw new SystemCodeDuplicateException(e);
		}
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#findById(java.lang.Long)
	 */
	public SystemCode findById(Long id) throws SystemCodeNotFoundException {
		try {
			return this.systemCodeDAO.findById(id);
		} catch (ObjectNotFoundException e) {
			throw new SystemCodeNotFoundException(e);
		}
	}

	/**
	 * @return the messageSource
	 */
	public MessageSource getMessageSource() {
		return messageSource;
	}

	/**
	 * @return the systemCodeDAO
	 */
	public ISystemCodeDAO getSystemCodeDAO() {
		return systemCodeDAO;
	}

	/**
	 * @return the systemEventPublisher
	 */
	public SystemEventPublisher getSystemEventPublisher() {
		return systemEventPublisher;
	}

	/**
	 * @see org.xlab.baseframework.service.api.IBaseService#parseFromVO(org.xlab.baseframework.domain.view.IViewDomainObject)
	 */
	public SystemCode parseFromVO(SystemCodeVO viewObject) {
		SystemCode pojo = new SystemCode();
		BeanUtils.copyProperties(viewObject, pojo,
				new String[] { SystemCodeVO.FIELD_CATEGORY });
		if (viewObject.getCategory() != null) {
			pojo.setCategory(this.parseFromVO(viewObject.getCategory()));
		}
		return pojo;
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#restoreSystemCode(org.xlab.baseframework.domain.SystemCode,
	 *      org.xlab.baseframework.domain.ISystemUserDomain)
	 */
	@Loggable
	public void restoreSystemCode(SystemCode systemCode,
			ISystemUserDomain restoreBy) {
		systemCode.setLastUpdatedBy(restoreBy.getUserCode());
		systemCode.setLastUpdatedByName(restoreBy.getRealName());
		systemCode.setLastUpdatedTime(new Date());
		String message = null;
		message = this.messageSource.getMessage(
				"info.service.syscode.restoring",
				new Object[] { systemCode.getCode() }, "Restoring SYSTEM code["
						+ systemCode.getCode() + "].", Locale.getDefault());
		log.debug(message);
		this.systemCodeDAO.logicRestore(systemCode);
		this.systemEventPublisher.publishEvent(new SystemCodeChangedEvent(this,
				systemCode));
	}

	/**
	 * @param messageSource
	 *            the messageSource to set
	 */
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	/**
	 * @param systemCodeDAO
	 *            the systemCodeDAO to set
	 */
	public void setSystemCodeDAO(ISystemCodeDAO systemCodeDAO) {
		this.systemCodeDAO = systemCodeDAO;
	}

	/**
	 * @param systemEventPublisher
	 *            the systemEventPublisher to set
	 */
	public void setSystemEventPublisher(
			SystemEventPublisher systemEventPublisher) {
		this.systemEventPublisher = systemEventPublisher;
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#updateSystemCode(org.xlab.baseframework.domain.SystemCode,
	 *      org.xlab.baseframework.domain.ISystemUserDomain)
	 */
	@Loggable
	public void updateSystemCode(SystemCode systemCode,
			ISystemUserDomain updatedBy) throws SystemCodeNotFoundException {
		try {
			SystemCode oldVersion = this
					.findByCode(systemCode.getCode(), false);
			oldVersion.setDescription(systemCode.getDescription());
			oldVersion.setSequence(systemCode.getSequence());
			oldVersion.setDeletedInd(systemCode.getDeletedInd());
			oldVersion.setCategory(systemCode.getCategory());
			oldVersion.setLastUpdatedBy(updatedBy.getUserCode());
			oldVersion.setLastUpdatedByName(updatedBy.getRealName());
			oldVersion.setLastUpdatedTime(new Date());
			String message = null;
			message = this.messageSource.getMessage(
					"info.service.syscode.updating",
					new Object[] { systemCode.getCode(),
							systemCode.getDescription(),
							systemCode.getSequence() }, "Updating SYSTEM code["
							+ systemCode.getCode() + "] with new description ["
							+ systemCode.getDescription()
							+ "] and new sequence [" + systemCode.getSequence()
							+ "].", Locale.getDefault());
			log.debug(message);
			this.systemCodeDAO.update(oldVersion);
			this.systemEventPublisher.publishEvent(new SystemCodeChangedEvent(
					this, oldVersion));
		} catch (SystemCodeNotFoundException e) {
			throw e;
		} catch (SystemCodeDuplicateException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @see org.xlab.baseframework.service.api.ISystemCodeService#findAllByCategory(org.xlab.baseframework.domain.SystemCode,
	 *      boolean, boolean)
	 */
	public List<SystemCode> findAllByCategory(SystemCode category,
			boolean useCache, boolean directlyOnly)
			throws SystemCodeNotFoundException {
		if (useCache) {
			List<SystemCode> directlyList = SystemRuntimeEnvironment
					.findAllCodeByCategory(category);
			if (directlyOnly)
				return directlyList;
			else {
				List<SystemCode> finalResult = new ArrayList<SystemCode>();
				finalResult.addAll(directlyList);
				for (SystemCode code : directlyList) {
					try {
						finalResult.addAll(this.findAllByCategory(code,
								useCache, directlyOnly));
					} catch (SystemCodeNotFoundException e) {
						log.debug("No more sub codes found!");
						continue;
					}
				}
				return finalResult;
			}
		} else {
			try {
				List<SystemCode> directlyList = this.systemCodeDAO
						.findAllByCategory(category);
				Collections.sort(directlyList);
				if (directlyOnly) {
					return directlyList;
				} else {
					List<SystemCode> finalResult = new ArrayList<SystemCode>();
					finalResult.addAll(directlyList);
					for (SystemCode code : directlyList) {
						try {
							finalResult.addAll(this.findAllByCategory(code,
									useCache, directlyOnly));
						} catch (SystemCodeNotFoundException e) {
							log.debug("No more sub codes found!");
							continue;
						}
					}
					return finalResult;
				}
			} catch (ObjectNotFoundException e) {
				throw new SystemCodeNotFoundException(e);
			}
		}
	}
}
